package com.wsc.personalblog.service.impl;

import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.wsc.personalblog.DTO.ArticleDTO;
import com.wsc.personalblog.DTO.FileDto;
import com.wsc.personalblog.utils.AliYunOssUtil;
import com.wsc.personalblog.common.Constant;
import com.wsc.personalblog.common.Result;
import com.wsc.personalblog.entity.*;
import com.wsc.personalblog.exception.ArticleSaveException;
import com.wsc.personalblog.mapper.ArticleMapper;
import com.wsc.personalblog.mapper.ArticleTagMapper;
import com.wsc.personalblog.mapper.TagMapper;
import com.wsc.personalblog.service.ArticleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;

@Service
@Slf4j
public class ArticleServiceImpl implements ArticleService {

    @Autowired
    private ArticleMapper articleMapper;

    @Autowired
    private TagMapper tagMapper;

    @Autowired
    private ArticleTagMapper articleTagMapper;


    /**
     * 更新文章 需要添加事务
     *
     * @param articleDTO 需要更新的新文章全部信息包括标签信息
     * @return
     */
    @Override
    @Transactional
    public Result<String> updateArticle(ArticleDTO articleDTO, String oldTitle) {
        //参数：标题、主体内容、分类id，发布时间，更新时间，标签信息
        //1. 分类id、发布时间不修改，其余可修改
        //2. 其中标签信息的修改，需要先删除旧的关联关系，再添加新的关联关系
        // 步骤如下：
        // 1. 根据文章id更新文章基本信息
        articleDTO.setUpdateDate(LocalDateTime.now());
        articleDTO.setContent(null);
        articleMapper.updateByPrimaryKeySelective(articleDTO);
        // 2. 通过中间表删除跟标签旧的关联关系
        Integer articleId = articleDTO.getId();
        ArticleTagExample articleTagExample = new ArticleTagExample();
        ArticleTagExample.Criteria criteria = articleTagExample.createCriteria();
        criteria.andArticleIdEqualTo(articleId);
        articleTagMapper.deleteByExample(articleTagExample);
        // 3. 通过中间表添加新的关联关系
        // 拿到标签信息
        List<Tag> tag = articleDTO.getTag();
        // 该方法传入文章id跟标签信息，即可帮助我们建立关联
        articleTagRelevancy(articleId, tag);
        updateFile(articleDTO, oldTitle);
        return Result.successNoData();
    }

    private void updateFile(ArticleDTO articleDTO, String oldTitle) {
        // 将旧文章改名保存
        String format = articleDTO.getFormat();
        String filename = oldTitle;
        String oldUrl = Constant.PATH + filename;
        String backupsUrl = Constant.PATH+"backups" + filename;
        File oldFile = new File(oldUrl);
        File backupsFile = new File(backupsUrl);
        oldFile.renameTo(backupsFile);
        // 创建新的文章，如果成功则删除保存的旧文章，失败则把旧文章恢复成原名
        try {
            uploadFile(articleDTO.getFile(), articleDTO.getTitle(),format);
            deleteFile("backups"+filename);
        } catch (IOException e) {
            backupsFile.renameTo(oldFile);
            throw new RuntimeException("文章更新失败");
        }

    }

    /**
     * 删除文章
     *
     * @param articleId 需要删除的文章的id
     * @return
     */
    @Override
    @Transactional
    public Result<String> removeArticle(Integer articleId, String fileName) {
        // 删除所有跟文章相关的评论
        articleMapper.removeCommentByArticleId(articleId);
        articleMapper.deleteByPrimaryKey(articleId);
        CompletableFuture.runAsync(()->{
            deleteFile(fileName);
        }, Executors.newFixedThreadPool(3));
        return Result.successNoData();
    }

    private void deleteFile(String fileName) {
        AliYunOssUtil.DeleteOne(fileName);
    }


    @Override
    public Integer getDrafts() {
        return articleMapper.getDraftsCount();
    }

    @Override
    public void unshelve(Article article) {
        articleMapper.updateByPrimaryKeySelective(article);
    }

    @Override
    public void shelve(Article article) {
        articleMapper.updateByPrimaryKeySelective(article);
    }

    @Override
    public PageInfo<ArticleDTO> getArticleInfo(Integer categoryId, Integer pageNum, Integer pageSize, String keyword) {
        pageSize = 100;
        if(categoryId == 0) categoryId = null;
        System.out.println("==============="+categoryId+"==================");
        // 开启分页
        Page<Article> page = PageHelper.startPage(pageNum, pageSize);
        // 获取跟此标签有关的文章id
        List<Integer> articleIdList = getArticleIdByKeyword(keyword);
        if (articleIdList == null || articleIdList.size() == 0) articleIdList = null;
        // 返回的实例为Page
        List<Article> article = articleMapper.getArticleInfoByCIdKey(categoryId, keyword, articleIdList);

        //      封装成PageInfo，有更强大的功能，传入两个参数，可以提供导航页
        PageInfo<ArticleDTO> pageInfo = new PageInfo(article, 7);
        // 查询各个文章的标签信息封装成ArticleDTO
        List<ArticleDTO> fullRes = article.stream().map((item) -> {
            Integer id = item.getId();
            // 查询到所有标签信息
            List<Tag> tagInfo = getTagInfoByArticleId(id);
            ArticleDTO articleDTO = new ArticleDTO();
            BeanUtils.copyProperties(item, articleDTO);
            articleDTO.setTag(tagInfo);
            return articleDTO;
        }).collect(Collectors.toList());
//         替换成最终完整的结果
        pageInfo.setList(fullRes);
        return pageInfo;
    }

    @Override
    public String uploadImg(MultipartFile file){
        UUID uuid = UUID.randomUUID();
        String fileName = uuid.toString();

        if(file != null){
            if(!file.isEmpty()){
                String originalFilename = file.getOriginalFilename();
                log.info("上传图片："+originalFilename);
                assert originalFilename != null;
                String[] split = originalFilename.split("\\.");
                fileName = fileName+"."+split[1];
                // 上传到阿里云OSS
                try {
                    AliYunOssUtil.upload(file.getBytes(), fileName);
                }catch (IOException e){
                    throw new RuntimeException("上传失败");
                }
            }
        }
        return Constant.OSS_ROOT_PATH+fileName;
    }

    @Override
    public void pubArticle(Map<String,Object> map) {
        String fileText = (String)map.get("fileText");
        String fileName = (String)map.get("fileName");
        String description = (String)map.get("description");
        Integer status = (Integer)map.get("status");
        String s = status.toString();
        Article article = new Article();
        article.setContent(fileName+".md");
        article.setDescription(description.length() == 0 ? null : description);
        article.setStatus(s);
        article.setTitle(fileName);
        article.setReleaseDate(LocalDateTime.now());
        article.setUpdateDate(LocalDateTime.now());
        try {
            articleMapper.insertSelective(article);
        }catch (Exception e){
            throw new RuntimeException("文章名称已存在");
        }
        AliYunOssUtil.upload(fileText.getBytes(), fileName+".md");
    }

    @Override
    public PageInfo<ArticleDTO> getDraftsInfo(Integer categoryId, Integer pageNum, Integer pageSize, String keyword) {
        pageSize = 100;
        if(categoryId == 0) categoryId = null;
        System.out.println("==============="+categoryId+"==================");
        // 开启分页
        Page<Article> page = PageHelper.startPage(pageNum, pageSize);
        // 获取跟此标签有关的文章id
        List<Integer> articleIdList = getArticleIdByKeyword(keyword);
        if (articleIdList == null || articleIdList.size() == 0) articleIdList = null;
        // 返回的实例为Page
        List<Article> article = articleMapper.getDraftsInfo(categoryId, keyword, articleIdList);

        //      封装成PageInfo，有更强大的功能，传入两个参数，可以提供导航页
        PageInfo<ArticleDTO> pageInfo = new PageInfo(article, 7);
        // 查询各个文章的标签信息封装成ArticleDTO
        List<ArticleDTO> fullRes = article.stream().map((item) -> {
            Integer id = item.getId();
            // 查询到所有标签信息
            List<Tag> tagInfo = getTagInfoByArticleId(id);
            ArticleDTO articleDTO = new ArticleDTO();
            BeanUtils.copyProperties(item, articleDTO);
            articleDTO.setTag(tagInfo);
            return articleDTO;
        }).collect(Collectors.toList());
//         替换成最终完整的结果
        pageInfo.setList(fullRes);
        return pageInfo;
    }

    @Override
    public Article getArticleInfoById(Integer articleId) {
        return articleMapper.selectByPrimaryKey(articleId);
    }

    @Override
    public void editArticle(Map<String, Object> map) {
        String fileText = (String)map.get("fileText");
        String fileName = (String)map.get("fileName");
        String description = (String)map.get("description");
        Integer status = (Integer)map.get("status");
        String articleId = (String)map.get("articleId");
        Integer categoryId = (Integer)map.get("categoryId");
        if(categoryId == null) categoryId = 0;
        Integer integer = new Integer(articleId);
        String s = status.toString();
        Article article = new Article();
        article.setContent(fileName+".md");
        article.setDescription(description.length() == 0 ? null : description);
        article.setStatus(s);
        article.setTitle(fileName);
        article.setId(integer);
        article.setCategoryId(categoryId == 0 ? null : categoryId);
        article.setUpdateDate(LocalDateTime.now());
        try {
            articleMapper.updateByPrimaryKeySelective(article);
        }catch (Exception e){
            throw new ArticleSaveException("文章名称已存在");
        }
        AliYunOssUtil.upload(fileText.getBytes(), fileName+".md");
    }


    /**
     * 发布新文章
     *
     * @param articleDTO 文章全部信息包括标签信息
     * @return
     */
    @Override
    @Transactional
    public Result<String> addArticle(ArticleDTO articleDTO) {
        articleDTO.setFile(articleDTO.getStrFile().getBytes());
        // 1.添加文章基本信息到到文章表
        articleDTO.setReleaseDate(LocalDateTime.now());
        articleDTO.setUpdateDate(LocalDateTime.now());
        articleDTO.setContent(articleDTO.getFormat());
        articleMapper.insert(articleDTO);
//        // 2.取出ArticleDTO的标签信息
//        List<Tag> tag = articleDTO.getTag();
//        // 拿到当前文章的id
//        Integer curArticleId = articleMapper.getLastArticleId();
//        // 3.通过中间表建立两者的关系，把两者id添加到中间表
//        // 该方法传入文章id跟标签信息，即可帮助我们建立关联
//        articleTagRelevancy(curArticleId, tag);
        try {
            uploadFile(articleDTO.getFile(), articleDTO.getTitle(), articleDTO.getFormat());
        } catch (IOException e) {
            // 保存失败，就删除残缺的文章
            deleteFile(articleDTO.getTitle()+"."+articleDTO.getFormat());
            throw new ArticleSaveException("文章保存失败");
        }
        return Result.successNoData();
    }

    private void uploadFile(byte[] file, String title, String format) throws IOException {
        String url = Constant.PATH + title+"."+format;
        BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(url));
        bos.write(file);
        bos.close();
    }

    //传入文章id，标签信息。即可建立两者关系
    private void articleTagRelevancy(Integer articleId, List<Tag> tagList) {
        return;
//        List<ArticleTag> articleTags = tagList.stream().map((item) -> {
//            ArticleTag articleTag = new ArticleTag();
//            articleTag.setArticleId(articleId);
//            articleTag.setTagId(item.getId());
//            return articleTag;
//        }).collect(Collectors.toList());
//        articleTagMapper.addBatch(articleTags);
    }

    /**
     * 分页查询所有文章,可根据关键字、分类id进行查询,不传分类id就查所有
     *
     * @param pageNum  页码
     * @param pageSize 每页数量
     * @param keyword  关键字
     * @return
     */
    @Override
    public PageInfo<ArticleDTO> getArticlePaging(Integer categoryId, Integer pageNum, Integer pageSize, String keyword) {
        pageSize = 100;
        if(categoryId == 0) categoryId = null;
        System.out.println("==============="+categoryId+"==================");
        // 开启分页
        Page<Article> page = PageHelper.startPage(pageNum, pageSize);
        // 获取跟此标签有关的文章id
        List<Integer> articleIdList = getArticleIdByKeyword(keyword);
        if (articleIdList == null || articleIdList.size() == 0) articleIdList = null;
        // 返回的实例为Page
        List<Article> article = articleMapper.getArticleByCIdKey(categoryId, keyword, articleIdList);

        //      封装成PageInfo，有更强大的功能，传入两个参数，可以提供导航页
        PageInfo<ArticleDTO> pageInfo = new PageInfo(article, 7);
        // 查询各个文章的标签信息封装成ArticleDTO
        List<ArticleDTO> fullRes = article.stream().map((item) -> {
            Integer id = item.getId();
            // 查询到所有标签信息
            List<Tag> tagInfo = getTagInfoByArticleId(id);
            ArticleDTO articleDTO = new ArticleDTO();
            BeanUtils.copyProperties(item, articleDTO);
            articleDTO.setTag(tagInfo);
            return articleDTO;
        }).collect(Collectors.toList());
//         替换成最终完整的结果
        pageInfo.setList(fullRes);
        return pageInfo;
    }

    /**
     * 分页查询所有文章,可根据关键字进行查询
     * 弃用
     *
     * @return
     */
    @Deprecated
    public PageInfo<ArticleDTO> getAllArticlePaging(Integer pageNum, Integer pageSize, String keyword) {
        // 开启分页
        Page<Article> page = PageHelper.startPage(pageNum, pageSize);
        ArticleExample articleExample = null;
        // 如果有关键字条件，那就添加标题条件，跟标签条件，两者是or关系
        if (!(keyword == null || keyword.trim().length() == 0)) {
            articleExample = new ArticleExample();
            ArticleExample.Criteria criteria = articleExample.createCriteria();
            // 标题条件
            criteria.andTitleLike(keyword);
            // 标签条件 先根据关键字去查询中间表找到跟该标签有关的文章id
            List<Integer> articleId = getArticleIdByKeyword(keyword);
            // 如果有查询到跟该标签有关的文章id就添加标签条件
            if (!(articleId == null || articleId.size() == 0)) {
                ArticleExample.Criteria criteria1 = articleExample.createCriteria();
                criteria1.andIdIn(articleId);
                articleExample.or(criteria1);
                articleExample.or(criteria1);
            }
        }
        // 返回的实例为Page
        List<Article> article = articleMapper.selectByExampleWithBLOBs(articleExample);
        //      封装成PageInfo，有更强大的功能，传入两个参数，可以提供导航页
        PageInfo<ArticleDTO> pageInfo = new PageInfo(article, 7);
        // 查询各个文章的标签信息封装成ArticleDTO
        List<ArticleDTO> fullRes = article.stream().map((item) -> {
            Integer id = item.getId();
            // 查询到所有标签信息
            List<Tag> tagInfo = getTagInfoByArticleId(id);
            ArticleDTO articleDTO = new ArticleDTO();
            BeanUtils.copyProperties(item, articleDTO);
            articleDTO.setTag(tagInfo);
            return articleDTO;
        }).collect(Collectors.toList());
        // 替换成最终完整的结果
        pageInfo.setList(fullRes);
        return pageInfo;
    }


    /**
     * 添加标签条件进行查询
     * 1. 根据keyword查询出所有的标签id
     * 2. 再根据标签id查询article_tag表，查询出带有此标签的文章id
     *
     * @return 返回跟此标签有关的文章id 查询不到就返回null，或者空的list
     */
    private List<Integer> getArticleIdByKeyword(String keyword) {
        if (keyword == null || keyword.trim().length() == 0) return null;
        // 根据keyword查询出所有的标签id
        TagExample tagExample = new TagExample();
        TagExample.Criteria tagCriteria = tagExample.createCriteria();
        tagCriteria.andNameLike(keyword);
        List<Tag> tags = tagMapper.selectByExample(tagExample);
        // 如果查询不到标签直接返回
        if (tags == null || tags.size() == 0) return null;
        // 再根据标签id查询article_tag表，查询出带有此标签的文章id
        List<Integer> tagId = tags.stream().map(Tag::getId).collect(Collectors.toList());
        ArticleTagExample articleTagExample = new ArticleTagExample();
        ArticleTagExample.Criteria criteria = articleTagExample.createCriteria();
        criteria.andTagIdIn(tagId);
        List<ArticleTag> articleTags = articleTagMapper.selectByExample(articleTagExample);
        // 返回文章id
        List<Integer> articleId = articleTags.stream().map(ArticleTag::getArticleId).collect(Collectors.toList());
        return articleId;
    }

    // 根据文章id查询所有标签信息
    private List<Tag> getTagInfoByArticleId(Integer articleId) {
        ArticleTagExample articleTagExample = new ArticleTagExample();
        ArticleTagExample.Criteria articleCriteria = articleTagExample.createCriteria();
        articleCriteria.andArticleIdEqualTo(articleId);
        /**
         * 下面两条sql可以合成一条
         * select * from article_tag where article_tag.article_id = (
         * select id from t_article
         * where category_id = 1)
         */
        // 查询出所有相关的标签信息
        List<ArticleTag> articleTags = articleTagMapper.selectByExample(articleTagExample);
        // 如果查询不到标签直接返回
        if (articleTags == null || articleTags.size() == 0) return null;
        // 过滤出所有标签id信息
        List<Integer> tagIdList = articleTags.stream().map(ArticleTag::getTagId).collect(Collectors.toList());
        TagExample tagExample = new TagExample();
        TagExample.Criteria tagCriteria = tagExample.createCriteria();
        tagCriteria.andIdIn(tagIdList);
        List<Tag> tags = tagMapper.selectByExample(tagExample);
        return tags;
    }


}
